环境
A8+集团版V8.0SP2
补丁对比
下载官网补丁
通过与A8+集团版V8.0SP2 中的seeyon-apps-edoc/com/seeyon/apps/govdoc/gb/util/OfdJavaZipUtil.class
对比
漏洞分析
发现在OfdJavaZipUtil.class#unzip
方法中有一处明显改动
在遍历压缩包内文件时,最新补丁做了如下改动
1 | public static void unzip(String fileName, String path, Map<String, String> charsetMap) { |
判断当前遍历的文件路径是否在指定的路径path
下,如果不在则不继续执行操作。
V8.0SP2 中的unzip
方法
1 | public static void unzip(String fileName, String path, Map<String, String> charsetMap) { |
在之前的版本中,并没有验证压缩包中文件路径是否存在传入的path
路径中,并且在104行处
直接将压缩包内文件名拼接到文件路径当中,然后再将压缩包内文件写入到该path+zn.getName()
的新创建的文件中。
这里如果我们在压缩包内构造一个文件名为../../test.jspx
文件,在没有更新补丁情况下,可以将文件写入到任意路径下。
以上,我们通过对比补丁,发现了OfdJavaZipUtil#unzip
解压文件名路径可控的漏洞利用的点,接下来,我们还需要找到fileName
变量可控一条漏洞利用的路径。
查找OfdJavaZipUtil
调用
在com/seeyon/apps/govdoc/gb/manager/impl/GovdocGBManagerImpl.java#getOfdMetadata
中调用了OfdJavaZipUtil#unzip
1 | private Map<String, Object> getOfdMetadata(Long fileId, File sourceFile, boolean onlyRead, Map<String, String> charsetMap) throws BusinessException { |
这个方法作用就是传入fileId
和sourceFile
,调用OfdJavaZipUtil.unzip
对sourceFile
解压,并获得解压路径下的OFD.xml
中的数据。我们主要关注sourceFile
变量是否可控,很显然,这个调用这个方法需要传入sourceFile
。
接下来,继续看哪儿调用了com/seeyon/apps/govdoc/gb/manager/impl/GovdocGBManagerImpl.java#getOfdMetadata
总共两个调用
1 | 1102行 |
这里主要通过1102行的GovdocGBManagerImpl.java#getOfdMetadata(Long fileId)
方法来触发调用。
继续回溯
GovdocGBManagerImpl.java#getOfdMetadata(Long fileId)
1 | public Map<String, Object> getOfdMetadata(Long fileId) throws BusinessException { |
在这个方法中,通过fileId
获取了对应的文件,这里获取到的文件则会传入到this.getOfdMetadata(fileId, file, true, (Map)null)
中,即可作为我们上一个方法的sourceFile
变量。
所以,到这里,如果fileId
可以通过外部控制,我们我们只需要找到一个可以上传zip
文件的地方,并且获取到fileId
,我们即可实现任意文件任意路径写入
继续回溯
在com/seeyon/apps/edoc/api/EdocApiImpl.java#getOfdMetaDataFromOfdFile(Long ofdFileId)
调用了getOfdMetadata(ofdFileId)
方法
继续回溯
在com/seeyon/ctp/common/content/mainbody/MainbodyController.java#invokingForm
中
1 | public ModelAndView invokingForm(HttpServletRequest request, HttpServletResponse response) throws BusinessException { |
在374
行调用了EdocApiImpl.java#getOfdMetaDataFromOfdFile
在369
行可以发现fileId
外部可控。
经过分析,执行到Map metaDataMap = this.edocApi.getOfdMetaDataFromOfdFile(Long.parseLong(ofdFileId));
需要满足几个条件。
isNew
参数值不能为Null
和false
subApp
只能等于2
继续回溯可发现调用到com/seeyon/ctp/common/content/mainbody/MainbodyController.java
的路由/content/content.do
熟悉致远的朋友应该都知道,通过路由调用MainbodyController
类的invokingForm
方法时,只需要在指定method
参数为invokingForm
即可
如
1 | http://xx.xx.xx.xx/seeyon/content/content.do?method=invokingForm |
到这里,我们一条漏洞利用的路线就走通了,
- 请求
http://xx.xx.xx.xx/seeyon/content/content.do?method=invokingForm
- 传入zip的
fileId
、isNew=ture
、subApp=2
。
- 传入zip的
- 调用
this.edocApi.getOfdMetaDataFromOfdFile(Long.parseLong(ofdFileId));
- 调用
this.govdocGBManager.getOfdMetadata(ofdFileId);
- 调用
this.getOfdMetadata(fileId, file, true, (Map)null);
- 调用
OfdJavaZipUtil.unzip(sourceFilePath, unzipFilePath, charsetMap);
- 控制
fos = new FileOutputStream(path + zn.getName());
中的zn.getName()
为../../../../ApacheJetspeed/webapps/ROOT/mzr.jspx
实现在任意文件写入。
到目前为止,我们距离getshell
只完成了一半,接下来,我们还需要制作一个特殊的zip
文件,并且找到一个可以上传zip
,并且获得该文件得fileId
的功能点。
zip制作
主要问题是在操作系统无法使用/
作为文件名使用,所以,这里通过制作一个携带webshell的压缩包,通过010 editor
修改文件名即可。(需要修改两处)
接下来,最最重要的,就是需要找到一个上传zip的地方。
那么要怎么找呢?
在com/seeyon/apps/govdoc/gb/manager/impl/GovdocGBManagerImpl.java#getOfdMetadata
方法中
通过fileId
获取文件,那么是否也可以保存文件呢?
在FileManager.java
接口中
可以发现是有save
接口的
接下来全局搜索fileManager.save
,还挺多
在com/seeyon/ctp/rest/resources/EditContentResource.java#saveFile()
方法中
1 | public Response saveFile() throws BusinessException { |
saveFile
方法中从request
请求中获取了fileId
、createDate
、notJinge2StandardOffice
等参数
84-103行从reques
请求中获取了上传的文件,并通过调用this.saveTempFile(tempFile, file)
将上传的文件保存在Seeyon/A8/base/temporary/
路径下
121行会先通过fileId
去查找V3XFile
对象,如果没有则重新创建,再设置fileId
、category
等属性值。
最后161行调用了this.fileManager.save(v3xfile);
通过该接口即可上传我们的zip文件,并且fileId
是我们可以控制的。
请求该接口的路径为
1 | http://xx.xx.xx.xx/seeyon/rest/editContent/saveFile |
漏洞利用
漏洞利用脚本
1 | import requests |